Skip to content

Make the getMessage function for the javascript example more robust. #39672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

qdirks
Copy link
Contributor

@qdirks qdirks commented May 25, 2025

The old getMessage function had a bug where if you made multiple unawaited calls to the function, it wouldn't behave correctly because previous reads had not yet completed, and all you were doing was reading 4 bytes at a time from the input into the header buffer. It also had a bug of not reading the correct amount of data, because the buffer given to the read function was not exactly sized for the expected message size, so it was possible that the amount of data that you got back was larger than the size that the header indicated if the extension had sent more than one message.

The new getMessage function solves both of those issues. The first issue is solved by using a promise and a while loop to trap the execution of extra calls at the top of the function while there is already a read in progress. Each call to getMessage is let through the trap one at a time as a read completes. If there are no pending reads, then a call to getMessage does not get trapped at the top of the function, because the read promise will be null, and so the loop will be skipped. The other issue is solved by using buffers which are appropriately sized according to the header information. I think this is also more succinct, since the old readFullAsync ultimately ended up doing that anyway at the end by passing the data array to the Uint8Array constructor. If you just create it from the header information and pass it directly to the read function, you can avoid dispatching to another function, and it prevents a bug.

Description

Make the getMessage function better for the javascript example.

Motivation

The change will prevent the bugs I described, which makes it easier to develop a native messaging extension for the next person, I hope.

Additional details

https://nodejs.org/api/fs.html#filehandlereadbuffer-options

Related issues and pull requests

The old getMessage function had an awful bug where if you made multiple unawaited calls to the function, it wouldn't behave correctly because previous reads had not yet completed, and all you were doing was reading 4 bytes at a time from the input into the header buffer. It also had an awful bug of not reading the correct amount of data, because the buffer given to the read function was not exactly sized for the expected message size, so it was possible that the amount of data that you got back was larger than the size that the header indicated if the extension had sent more than one message.

The new getMessage function solves both of those issues. The first issue is solved by using a promise and a while loop to trap the execution of further calls at the top of the function while there is already a read in progress. Each call to getMessage is let through the trap one at a time as a read completes. If there are no pending reads, then a call to getMessage does not get trapped at the top of the function, because the read promise will be null, and so the loop will be skipped. The other issue is solved by using buffers which are appropriately sized according to the header information. I think this is also more succinct, since the old `readFullAsync` ultimately ended up doing that anyway at the end by passing the data array to the Uint8Array constructor. If you just create it from the header information and pass it directly to the read function, you can avoid dispatching to another function, and it prevents a bug.
@qdirks qdirks requested a review from a team as a code owner May 25, 2025 12:41
@qdirks qdirks requested review from rebloor and removed request for a team May 25, 2025 12:41
@github-actions github-actions bot added Content:WebExt WebExtensions docs size/s [PR only] 6-50 LoC changed labels May 25, 2025
Copy link
Contributor

github-actions bot commented May 25, 2025

Preview URLs

(comment last updated: 2025-05-30 04:10:38)

Copy link
Member

@Josh-Cena Josh-Cena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure your code follows our style guide. In particular:

  1. Do not use underscore-prefixed variables, and use camelCase.
  2. Names like rs_rp and rp don't mean anything to me. Use full words that make sense.
  3. There's no bug about "unawaited calls", because all calls are awaited in our example (we show you how getMessage should be used). If your calls to getMessage are different then that's really a bug in your code. You seem to be implementing a lock with your _rp but again the variables are poorly named and I can't read the code. I wouldn't recommend implementing a lock yourself though; it's too much mechanism for a trivial demo.

I understand the part about the size mismatch though. I don't know why the original version is using a loop instead of preallocating the whole thing.

@rebloor rebloor requested a review from dotproto May 26, 2025 17:35
dotproto and others added 4 commits May 26, 2025 16:36
Changes made by [mdn-linter] in PR mdn#39672.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Change the naming of variables to match the style on MDN. Try to make the code a little easier to understand.
@qdirks
Copy link
Contributor Author

qdirks commented May 27, 2025

@Josh-Cena Thank you for your feedback. I made some changes to it which I hope you'll accept. Totally understand if you don't want to put this on MDN, though. Let me know if you would accept it if the variables were named differently or something like that. Thanks.

@Josh-Cena
Copy link
Member

Josh-Cena commented May 27, 2025

Thanks, this looks much better now. I made some tiny updates to your code, mainly wrapping it with try...finally to make sure the lock gets released in all cases. This looks good to me stylistically, but I'm unaware whether it's fully correct and whether the looping read in the original code is intended, so I'll defer to the WebExt team to review it.

@Josh-Cena Josh-Cena dismissed their stale review May 27, 2025 10:47

Addressed

@qdirks
Copy link
Contributor Author

qdirks commented May 27, 2025

@Josh-Cena Good idea on the try/finally

Josh-Cena and others added 2 commits May 28, 2025 10:46
Co-authored-by: rebloor <git@sherpa.co.nz>
The `try` block is wrapping the calls to `input.read` because those calls can sometimes fail with an error. If they do, then we want to make sure that we still close the open file handle.
@qdirks
Copy link
Contributor Author

qdirks commented May 29, 2025

One more tiny change, which is just to close the file handle from within the finally block as well.

@qdirks qdirks marked this pull request as draft May 30, 2025 01:01
Large messages from the extension may not be read all in one go, so you need to loop to get the entire message, and add the bytesRead from each read to an offset value which allows you to continue reading the next chunk of data into your buffer at that offset.
qdirks and others added 2 commits May 29, 2025 21:29
…ex.md

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@qdirks
Copy link
Contributor Author

qdirks commented May 30, 2025

Oops, I need the loop to be able to get the entire message. But we can do better than the original code by keeping track of the offset into the message buffer where new data needs to be inserted, and by not reading more than the length indicated by the header by passing that offset to the read method along with the message buffer. I think... I think this is ready now. I'll grant you, the code became longer than I had hoped, but I think this finally solves all of the issues I had.

2 notes which you may want to add about the example:

  • This code won't work on windows, because it accesses a file path ('/dev/stdin') which does not exist on windows.
  • The maximum string size in NodeJS is somewhere around 500 megabytes, at least on my machine. I wasn't able to create strings larger than that. You might want to know that if you're trying to TextDecoder.decode the message into a string, and then JSON.parse it, since everything the extension sends to your native app is JSON.

I may eventually try to re-write this to work on both Linux and Windows using all of the knowledge I gained here, unless somebody beats me to it.

@qdirks qdirks marked this pull request as ready for review May 30, 2025 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Content:WebExt WebExtensions docs size/s [PR only] 6-50 LoC changed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants